//
// gcalc.m
//
// A simple program that illustrates basic Objective-C programming along with
// basic usage of some Cocoa (actually, "Foundation") classes, in the form of
// a command line calculator that uses the Google calculator to do the real
// computing.  It simply makes a big string by concatenating all of its 
// command line args, runs it as a Google query, then tries to parse a
// Google calculator answer out of the resulting HTML.
//
// Compile with (requires Tiger or later) if not in Xcode:
//   gcc -o gcalc gcalc.m -framework Foundation -Wall -Werror -std=c99 -Wall
//
//  Created by Greg Miller on 10/14/06.
//  Copyright Greg Miller 2006 . All rights reserved.
//

#import <Foundation/Foundation.h>

// Will be set to YES if the "-d" flag was specified
static BOOL gDebug = NO;

//
// GetStringForArgs
//
// Returns a string created by joining argv by a space.
//
static NSString *GetStringForArgs(int argc, char *argv[]) {
  NSMutableString *argString = [NSMutableString string];
  for (int i = 1; i < argc; i++) {
    // If one of the args is "-d", set the global gDebug flag
    if (strcmp(argv[i], "-d") == 0) {
      gDebug = YES;
      continue;
    }
    [argString appendFormat:@"%s%s",
                i == 1 ? "" : " ",  // Don't put a space before the first one
                argv[i]];
  }
  return argString;
}

//
// RunQuery
//
// Does a Google query and returns the resulting HTML as an NSXMLDocument.
//
static NSXMLDocument *RunQuery(NSString *query) {
  if (!query)
    return nil;
  
  NSString *escapedQuery = [query stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
  NSMutableString *mutableQuery = [[escapedQuery mutableCopy] autorelease];
  [mutableQuery replaceOccurrencesOfString:@"+"
                                withString:@"%2B"
                                   options:0
                                     range:NSMakeRange(0, [mutableQuery length])];
  
  NSString *urlString = [NSString stringWithFormat:@"http://www.google.com/search?q=%@", mutableQuery];
  NSURL *url = [NSURL URLWithString:urlString];
  
  if (gDebug)
    NSLog(@"url = %@", url);
  
  NSError *error = nil;
  // This handles all the HTTP fetching details.
  NSXMLDocument *doc = [[[NSXMLDocument alloc] initWithContentsOfURL:url
                                                             options:NSXMLDocumentTidyHTML
                                                               error:&error] autorelease];
  
  if (!doc)
    fprintf(stderr, "Failed to get XML document from '%s', error = %s\n",
            [urlString UTF8String], [[error description] UTF8String]);

  return doc;
}

//
// GetAnswerFromXMLDocument
//
// Extracts the answer to a Google calculator query out of the given "doc".
//
static NSString *GetAnswerFromXMLDocument(NSXMLDocument *doc) {
  if (gDebug)
    NSLog(@"%@", [doc XMLStringWithOptions:NSXMLNodePrettyPrint]);
  
  NSError *error = nil;
  NSArray *nodes = [doc nodesForXPath:@".//div[@id='res']/table[1]/tr[1]/td[3]/h2/b"
                                error:&error];
  if (gDebug)
    NSLog(@"error = %@", error);
  
  if ([nodes count] < 1)
    return nil;
  
  return [[nodes objectAtIndex:0] stringValue];
}

//
// main
//
// See comments at the top of the file.
//
int main(int argc, char *argv[]) {
  if (argc < 2) {
    const char *usage =
      "gcalc version 0.5 by Greg Miller\n\n"
      "Usage: gcalc [-d] <calculator query>\n\n"
      "example:  gcalc \"5+2*2\"\n"
      "example:  gcalc 5!\n"
      "example:  gcalc \"sqrt(-4)\"\n"
      "example:  gcalc \"160 pounds * 4000 feet in calories\"\n"
      "example:  gcalc avogadros number\n"
      "example:  gcalc 0b110111010 + 0x33 in decimal\n"
      "example:  gcalc 22 lira in yen\n"
      "example:  gcalc 2 to the power of 5\n";
    fprintf(stderr, "%s", usage);
    exit(1);
  }
  
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  NSString *query = GetStringForArgs(argc, argv);
  NSXMLDocument *doc = RunQuery(query);
  NSString *answer = GetAnswerFromXMLDocument(doc);
  
  if (answer)
    printf("%s\n", [answer UTF8String]);
  else
    fprintf(stderr, "No answer found.\n");

  [pool release];
  return 0;
}



